package multithreading;

/**
 * @Author: 海琳琦
 * @Date: 2022/4/2 21:58
 * 1、问题：买票时存在重票、错票---》出现线程安全问题。
 * 2、原因：当某个线程操作车票的过程中，尚未操作完全时，其他线程此时也参与进来也操作车票。
 * 3、解决：当一个线程在操作时，其他线程不能参与进来，直到操作完成。
 * 4、java中通过同步机制解决线程安全问题。
 * 方式一、同步代码块
 *         synchronized(同步监视器){
 *             //需要被同步的代码（不能包多，包多性能降低；不能包少，线程不安全）
 *         }
 *         说明：1、操作共享数据（多个线程共同操作的变量）的代码，即为需要被同步的代码
 *               2、同步监视器：俗称“锁”。任何一个类的对象（对象唯一），都可以充当一个锁。  要求：多个线程共用同一把锁
 * 方式二、同步方法
 *         如果操作共享数据的代码完整地声明在一个方法中，我们不妨将此方法声明为同步方法（修饰符synchronized)
 *         非静态同步方法：默认同步监视器this       静态同步方法：默认同步监视器 类.class
 * 方法三、Lock锁（接口）  常用ReentrantLock实现Lock接口
 *          ReentrantLock：拥有与synchronized相同的并发性和内存语义
 *
 * lock锁与与synchronized的区别：lock锁是手动加锁和释放锁，更加灵活。synchronized的是自动加锁和释放锁。
 * 建议使用顺序为Lock锁、同步代码块、同步方法。
 *
 * 同步的方式：解决线程安全问题，但性能降低了，相当于单线程
 */

class Ticket implements Runnable {
    /**
     * 共享数据（实现Runnable接口的方式天然共享数据）
     */
    private int tick = 10011;

    /**
     * 该对象只创建一次（或者使用当前对象，用this表示）
     */
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //同步代码块，多个线程使用同一把锁  // synchronized(obj)
//            synchronized(this){
//                if (tick > 0) {
//                    //（未加入同步代码块时）加入此段代码，出现错误的几率增大
////                try {
////                    Thread.sleep(100);
////                } catch (InterruptedException e) {
////                    e.printStackTrace();
////                }
//                    System.out.println(Thread.currentThread().getName() + " 售出车票，tick号为" + tick--);
//                }else{
//                    break;
//                }
                judge();
//            }
        }
    }

    private synchronized void judge(){
        if (tick > 0) {
            System.out.println(Thread.currentThread().getName() + " 售出车票，tick号为" + tick--);
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread window1 = new Thread(ticket);
        Thread window2 = new Thread(ticket);
        Thread window3 = new Thread(ticket);
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}
